package net.pierrerenaudin.blobbyvolley.client;

public class PhysicEngine {
	
	private Player leftPlayer;
	private Player rightPlayer;
	private Ball ball;
	private KeyboardManager keyboardManager;
	
	public static double TIMEOUT_MAX = 2.5;

	//Blobby Settings
	public static double BLOBBY_HEIGHT = 89.0;
	public static double BLOBBY_WIDTH = 75.0;
	public static double BLOBBY_UPPER_SPHERE = 19.0;
	public static double BLOBBY_UPPER_RADIUS = 25.0;
	public static double BLOBBY_LOWER_SPHERE = 13.0;
	public static double BLOBBY_LOWER_RADIUS = 33.0;

	// Volley Ball Net
	public static double NET_POSITION_X = 400.0;
	public static double NET_POSITION_Y = 438.0;
	public static double NET_RADIUS = 7.0;
	public static double NET_SPHERE = 154.0;
	public static double NET_SPHERE_POSITION = 284.0;

	//Ball Settings
	public static double BALL_RADIUS = 31.5;

	public static double GROUND_PLANE_HEIGHT_MAX = 500.0;
	public static double GROUND_PLANE_HEIGHT = GROUND_PLANE_HEIGHT_MAX - BLOBBY_HEIGHT / 2.0;

	//Border Settings
	public static double LEFT_PLANE = 0.0;
	public static double RIGHT_PLANE = 800.0;

	// Gamefeeling relevant constants
	public static double BLOBBY_ANIMATION_SPEED = 0.5;
	public static double BLOBBY_JUMP_ACCELERATION = 15.1;
	public static double BLOBBY_SPEED = 4.5;

	// This is exactly the half of the gravitation
	public static double BLOBBY_JUMP_BUFFER = 0.44;
	public static double GRAVITATION = 0.88;
	public static double BALL_GRAVITATION = 0.28;
	public static double STANDARD_BALL_ANGULAR_VELOCITY = 0.1;
	public static double STANDARD_BALL_HEIGHT = 269.0 + BALL_RADIUS;

	public static double BALL_COLLISION_VELOCITY = 13.125;

	// Other
	public boolean gameRunning = false;
	public boolean ballValid = true;
	public double lastHitIntensity = 0;
	public double timeSinceBallout = 0;
	
	public PhysicEngine(GameController controller){
		leftPlayer = controller.getLeftPlayer();
		rightPlayer = controller.getRightPlayer();
		ball = controller.getBall();
		keyboardManager = controller.getKeyboardManager();
		
		// Init
		reset(leftPlayer);
	}
	
	public void update(){
		// Compute independent actions
		handleBlob(leftPlayer);
		handleBlob(rightPlayer);
		
		// Ball Gravitation
		if (gameRunning)
		{
			ball.velocity.y += BALL_GRAVITATION;
		}	

		// move ball
		ball.position.y += ball.velocity.y;
		ball.position.x += ball.velocity.x;

		
		// Collision detection
		if(ballValid)
		{
			checkBlobbyBallCollision(leftPlayer);
			checkBlobbyBallCollision(rightPlayer);
		}
		// Ball to ground Collision
		else if (ball.position.y + BALL_RADIUS > 500.0)
		{
			ball.velocity = ball.velocity.reflectY().scaleY(0.5);
			ball.velocity = ball.velocity.scaleX(0.55);
			ball.position.y = 500.0 - BALL_RADIUS;
		}
		
		if (ball.hitPlayer(leftPlayer) || ball.hitPlayer(rightPlayer))
		{
			gameRunning = true;
		}
		
		// Border Collision
		if (ball.position.x - BALL_RADIUS <= LEFT_PLANE && ball.velocity.x < 0.0)
		{
			ball.velocity = ball.velocity.reflectX();
		}
		else if (ball.position.x + BALL_RADIUS >= RIGHT_PLANE && ball.velocity.x > 0.0)
		{
			ball.velocity = ball.velocity.reflectX();
		}
		else if (ball.position.y > NET_SPHERE_POSITION &&
				Math.abs(ball.position.x - NET_POSITION_X) < BALL_RADIUS + NET_RADIUS)
		{
			ball.velocity = ball.velocity.reflectX();
			ball.position = ball.position.addVector(ball.velocity);
		}
		else
		{
			// Net Collisions
			Vector tmp = new Vector(0,0);
			tmp.vectorByDots(ball.position, new Vector(NET_POSITION_X, NET_SPHERE_POSITION));
			double ballNetDistance = tmp.getLength();

			if (ballNetDistance < NET_RADIUS + BALL_RADIUS)
			{ 
				tmp.vectorByDots(ball.position, new Vector(NET_POSITION_X, NET_SPHERE_POSITION));
				tmp = tmp.normalise();
				ball.velocity = ball.velocity.reflect(tmp);
				ball.velocity = ball.velocity.scale(0.75);
				while (ballNetDistance < NET_RADIUS + BALL_RADIUS)
				{
					ball.position = ball.position.addVector(ball.velocity);
					tmp.vectorByDots(ball.position, new Vector(NET_POSITION_X, NET_SPHERE_POSITION));
					ballNetDistance = tmp.getLength();
				}
			}
		}
		// Collision between blobby and the net
		if ((leftPlayer.position.x + BLOBBY_LOWER_RADIUS) > NET_POSITION_X - NET_RADIUS)
		{
			leftPlayer.position.x = NET_POSITION_X - NET_RADIUS - BLOBBY_LOWER_RADIUS;
		}

		if ((rightPlayer.position.x - BLOBBY_LOWER_RADIUS) < NET_POSITION_X + NET_RADIUS)
		{
			rightPlayer.position.x = NET_POSITION_X + NET_RADIUS + BLOBBY_LOWER_RADIUS;
		}
		
		// Collision between blobby and the border
		if (leftPlayer.position.x < LEFT_PLANE)
		{
			leftPlayer.position.x = LEFT_PLANE;
		}

		if (rightPlayer.position.x > RIGHT_PLANE)
		{
			rightPlayer.position.x = RIGHT_PLANE;
		}
	}
	
	public void dampBall(){
		ball.velocity = ball.velocity.scale(0.6);
	}

	public void setBallValidity(boolean ballValidity) {
		this.ballValid = ballValidity;
	}

	public boolean isBallValidity() {
		return ballValid;
	}
	
	public boolean roundFinished(){
		if (resetAreaClear())
		{
			if (!ballValid)
			{
				if (ball.velocity.y < 1.5 && ball.velocity.y > -1.5 && ball.position.y > 430)
				{
					return true;
				}
			}
		}
		if (timeSinceBallout > TIMEOUT_MAX)
		{
			return true;
		}
		return false;
	}
	
	public boolean resetAreaClear(){
		if (blobbyHitGround(leftPlayer) && blobbyHitGround(rightPlayer))
			return true;
		return false;
	}
	
	public void resetPlayer()
	{
		leftPlayer.position.x = 200.0;
		leftPlayer.position.y = GROUND_PLANE_HEIGHT;
		rightPlayer.position.x = 600.0;
		rightPlayer.position.y = GROUND_PLANE_HEIGHT;
	}
	
	public void reset(Player player){
		if (player.side == Player.LEFT_PLAYER)
		{
			ball.position = new Vector(200.0, STANDARD_BALL_HEIGHT);
		}
		else
		if (player.side == Player.RIGHT_PLAYER)
		{
			ball.position = new Vector(600.0, STANDARD_BALL_HEIGHT);
		}
		else
		{
			ball.position = new Vector(400.0, 450.0);
		}

		ball.velocity.clear();

		leftPlayer.state = 0.0;
		rightPlayer.state = 0.0;
		
		gameRunning = false;
		ballValid = true;

		lastHitIntensity = 0.0;	
	}
	
	public boolean blobbyHitGround(Player player){
		if (player.side == Player.LEFT_PLAYER)
		{
			if (leftPlayer.position.y >= GROUND_PLANE_HEIGHT)
			{
				return true;
			}
			else
			{
				return false;
			}
		}
		else if (player.side == Player.RIGHT_PLAYER)
		{
			if (player.position.y >= GROUND_PLANE_HEIGHT)
			{
				return true;
			}
			else
			{
				return false;
			}
		}
		else
		{
			return false;
		}
	}
	
	public void blobbyStartAnimation(Player player){
		if (player.animationSpeed == 0.0)
		{
			player.animationSpeed = BLOBBY_ANIMATION_SPEED;
		}
	}
	
	public void blobbyAnimationStep(Player player){
		if (player.state < 0.0)
		{
			player.animationSpeed = 0.0;
			player.state = 0.0;
		}
		if (player.state >= 3.5)
		{
			player.animationSpeed =- BLOBBY_ANIMATION_SPEED;
		}

		player.state += player.animationSpeed;

		if (player.state >= 4.0)
		{
			player.state = 4.0;
		}
	}
	
	public void handleBlob(Player player){
		// Reset ball to blobby collision
		ball.setHitPlayer(player, false);
		if (keyboardManager.isKeyPressForPlayer(player, KeyboardManager.UP))
		{
			if (blobbyHitGround(player))
			{
				player.velocity.y = -BLOBBY_JUMP_ACCELERATION;
				blobbyStartAnimation(player);
			}
			player.velocity.y -= BLOBBY_JUMP_BUFFER;
		}
		if ((keyboardManager.isKeyPressForPlayer(player, KeyboardManager.LEFT) 
				|| keyboardManager.isKeyPressForPlayer(player, KeyboardManager.RIGHT))
				&& blobbyHitGround(player))
		{
			blobbyStartAnimation(player);
		}

		player.velocity.x =
			(keyboardManager.isKeyPressForPlayer(player, KeyboardManager.RIGHT) ? BLOBBY_SPEED : 0.0) -
			(keyboardManager.isKeyPressForPlayer(player, KeyboardManager.LEFT) ? BLOBBY_SPEED : 0.0);

		// Acceleration Integration
		player.velocity.y = player.velocity.y + GRAVITATION;

		// Compute new position
		player.addVector(player.velocity);

		if (player.position.y > GROUND_PLANE_HEIGHT)
		{
			if(player.velocity.y > 3.5)
			{
				blobbyStartAnimation(player);
			}
			
			player.position.y = GROUND_PLANE_HEIGHT;
			player.velocity.y = 0.0;
		}
		blobbyAnimationStep(player);
	}
	
	public void checkBlobbyBallCollision(Player player){
		Vector tmp = new Vector(0,0);
		Vector blobpos;
		Vector circlepos;
		// Check for bottom circles
		if(playerBottomBallCollision(player))
		{
			tmp.vectorByDots(ball.velocity, player.velocity);
			lastHitIntensity = tmp.getLength();

			blobpos = player.position;
			circlepos = new Vector(blobpos.x, blobpos.y + BLOBBY_LOWER_SPHERE);

			tmp.vectorByDots(ball.position, circlepos);
			tmp.x = -tmp.x;
			tmp.y = -tmp.y;
			ball.velocity = tmp;

			ball.velocity = ball.velocity.normalise();
			ball.velocity = ball.velocity.scale(BALL_COLLISION_VELOCITY);
			ball.position = ball.position.addVector(ball.velocity);
			ball.setHitPlayer(player, true);
		}
		else if(playerTopBallCollision(player))
		{
			tmp.vectorByDots(ball.velocity, player.velocity);
			lastHitIntensity = tmp.getLength();

			blobpos = player.position;
			circlepos = new Vector(blobpos.x, blobpos.y - BLOBBY_UPPER_SPHERE);

			tmp.vectorByDots(ball.position, circlepos);
			tmp.x = -tmp.x;
			tmp.y = -tmp.y;
			ball.velocity = tmp;

			ball.velocity = ball.velocity.normalise();
			ball.velocity = ball.velocity.scale(BALL_COLLISION_VELOCITY);
			ball.position = ball.position.addVector(ball.velocity);
			ball.setHitPlayer(player, true);
		}
	}
	
	public boolean playerTopBallCollision(Player player)
	{
		Vector tmp = new Vector(0.0, 0.0);
		tmp.vectorByDots(ball.position, new Vector(player.position.x, player.position.y - BLOBBY_UPPER_SPHERE));
		if(tmp.getLength() <= BALL_RADIUS + BLOBBY_UPPER_RADIUS)
		{
			return true;
		}
		return false;
	}

	public boolean playerBottomBallCollision(Player player)
	{
		Vector tmp = new Vector(0.0, 0.0);
		tmp.vectorByDots(ball.position, new Vector(player.position.x, player.position.y + BLOBBY_LOWER_SPHERE));
		if(tmp.getLength() <= BALL_RADIUS + BLOBBY_LOWER_RADIUS)
			return true;
		return false;
	}
	
	public void cleanPhysic(){
		gameRunning = false;
		ballValid = true;
		lastHitIntensity = 0;
		ball.setHitPlayer(leftPlayer, false);
		ball.setHitPlayer(rightPlayer, false);
		timeSinceBallout = 0;

		leftPlayer.velocity.x = 0.0;
		leftPlayer.velocity.y = 0.0;
		rightPlayer.velocity.x = 0.0;
		rightPlayer.velocity.y = 0.0;

		leftPlayer.animationSpeed = 0.0;
		leftPlayer.state = 0.0;
		rightPlayer.animationSpeed = 0.0;
		rightPlayer.state = 0.0;
	}
	
	public boolean ballHitRightGround()
	{
		if(ballValid)
		{
			if ((ball.position.y > GROUND_PLANE_HEIGHT) && (ball.position.x > NET_POSITION_X))
			{
				return true;
			}
		}
		return false;
	}

	public boolean ballHitLeftGround()
	{
		if(ballValid)
		{
			if (ball.position.y > GROUND_PLANE_HEIGHT && ball.position.x < NET_POSITION_X)
			{
				return true;
			}
		}
		return false;
	}
}
